using System;
using System.Collections.Generic;
using System.Text;
using System.Data;

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using Microsoft.SharePoint.WebControls;

using Microsoft.Win32;

using Aras.IOM;

namespace Aras.WebParts
{
    /// <summary>
    /// The web part shows items from Aras Innovator.
    /// </summary>
    /// <remarks>
    /// Innovator server URL, database and user credentials are the web part parameters (Misc. group of params) 
    /// and could be set throught standard "Modify Shared Web Part" menu. Type of shown items as well as table
    /// structure (columns, alignment, etc.) is defined by the web part parameter 'SharePoint Grid Name' which
    /// must be define in the Innovator that the web part connects to. 
    /// </remarks>
    public class ItemGridWebPart : AjaxBaseWebpart
    {
        public class DataTableWrapper 
        { 
            private DataTable _dt = new DataTable();
            public DataTableWrapper(DataTable dt) 
            { 
                _dt = dt; 
            } 
            
            public DataTable GetTable() 
            { return _dt; } 
        }

        public class GridDefinition
        {
            private Item _gdef;
            private SortedList<int, Item> _columns;

            public GridDefinition(Item gd)
            {
                _gdef = gd;

                Item crels = _gdef.getRelationships("SharePoint Grid Column");
                _columns = new SortedList<int, Item>();
                int count = crels.getItemCount();
                for (int i = 0; i < count; i++)
                {
                    Item c = crels.getItemByIndex(i);
                    _columns.Add(Int32.Parse(c.getProperty("sort_order")), c);
                }
            }

            public string Query
            {
                get { return _gdef.getProperty("query"); }
            }

            public SortedList<int, Item> Columns
            {
                get { return _columns; }
            }
        }
        
        // Default values for web part parameters
        private string _url = @"http://sql08demo:88"; /*"http://localhost:8008";*/
        private string _db = @"InnovatorSolutions"; /*"InnovatorSolutions";*/
        private bool _auto_search = false;
        private string _grid_name = string.Empty; 

        // Data members
        private DataTable sourceDataTable;
        private DataTableWrapper myDataTable;

        private UserControl _search;
        private SPGridView _grid;
        private TextBox _criterionTxt;
        private string _criterion;
        private Boolean _data_populated = false;
        private Exception _exc = null;
 
        /// <summary>
        /// Innovator server URL
        /// </summary>
        [Personalizable(PersonalizationScope.Shared),
         WebBrowsable(true),
         WebDisplayName("Innovator URL"),
         WebDescription("Set Innovator URL here (e.g. http://InnServer/MyInnovator)")]
        public string Url
        {
            get {return _url;}
            set { _url = value; }
        }
        
        /// <summary>
        /// Name of the Innovator database that is accessed by the web part
        /// </summary>
        [Personalizable(PersonalizationScope.Shared),
         WebBrowsable(true),
         WebDisplayName("Innovator Database"),
         WebDescription("Set Innovator Database here (e.g. SolutionsDb)")]
        public string Db
        {
            get { return _db; }
            set { _db = value; }
        }

        /// <summary>
        /// If 'true' then the grid is auto-filled; otherwise only when user clicked on 'Search'
        /// </summary>
        [Personalizable(PersonalizationScope.Shared),
         WebBrowsable(true),
         WebDisplayName("Auto Fill the Grid"),
         WebDescription("Set to 'false' if grid must be filled on 'Search' only")]
        public bool IsAutoSearch
        {
            get { return _auto_search; }
            set { _auto_search = value; }
        }

        /// <summary>
        /// Name of the SharePoint Grid definition.
        /// </summary>
        [Personalizable(PersonalizationScope.Shared),
         WebBrowsable(true),
         WebDisplayName("'SharePoint Grid' Name"),
         WebDescription("Set name of 'SharePoint Grid' here")]
        public string SharePointGrid
        {
            get { return _grid_name; }
            set { _grid_name = value; }
        }

        private string SPUrl
        {
            get
            {
                string key = "ItemGridWebPart - host URL";
                string spu = (string)HttpContext.Current.Session[key];
                if (spu == null)
                {
                    string lp = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path);
                    HttpContext.Current.Session[key] = lp.Substring( 0, lp.LastIndexOf( "/" ) );
                    spu = (string)HttpContext.Current.Session[key];
                }

                return spu;
            }
        }

        private Innovator InnovatorHandle
        {
            get
            {
                string key = string.Format("Aras Innovator: {0} {1}", Url, Db);
                Innovator inn = (Innovator)HttpContext.Current.Session[key];
                if (inn == null)
                {
                    HttpContext.Current.Session[key] = _doLoginToInnovator();
                    inn = (Innovator)HttpContext.Current.Session[key];
                }

                return inn;
            }
        }

        private GridDefinition SPGridDef
        {
            get
            {
                if (SharePointGrid == null || SharePointGrid.Trim().Length == 0)
                    return null;

                string key = string.Format("SharePoint Grid Definition: {0}", SharePointGrid);
                GridDefinition gridDef = (GridDefinition)HttpContext.Current.Session[key];
                if (gridDef == null)
                {
                    HttpContext.Current.Session[key] = _loadShareGridDefinition();
                    gridDef = (GridDefinition)HttpContext.Current.Session[key];
                }

                return gridDef;
            }
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            // Load 'Search Criteria' user part
            this._search = (UserControl)Page.LoadControl(@"~/_CONTROLTEMPLATES/ArasSearchCriteria/SearchCriteria.ascx");
            ImageButton btnRun = (ImageButton)this._search.FindControl("ArasRunQueryBtn");
            btnRun.Click += new ImageClickEventHandler(btnRun_click);
            this._criterionTxt = (TextBox)this._search.FindControl("ArasFilterTxt");
            this._criterion = this._criterionTxt.Text = (string)this.ViewState["SearchCriterion"];
            
            UpdatePanel panel = new UpdatePanel();
            panel.ChildrenAsTriggers = true;
            panel.UpdateMode = UpdatePanelUpdateMode.Conditional;
            panel.ContentTemplateContainer.Controls.Add(this._search);

            if (this.SharePointGrid == null || this.SharePointGrid.Trim().Length == 0)
                return;

            try
            {
                _createDataTable();
                _createSourceObject(panel.ContentTemplateContainer.Controls);
                _createSPGridView(panel.ContentTemplateContainer.Controls);
                if( !IsAutoSearch )
                    this._data_populated = true;
            }
            catch (Exception exc)
            {
                this._exc = exc;
            }
            this.Controls.Add(panel);
        }
        
        protected void btnRun_click(object s, ImageClickEventArgs e)
        {
            this.ViewState["SearchCriterion"] = this._criterion = this._criterionTxt.Text;
            _populateDataTable();
            this._grid.DataBind();
            this._data_populated = true;
        }

        protected override void OnPreRender(EventArgs e)
        {
            if (this.sourceDataTable != null)
            {
                try
                {
                    if (this._data_populated == false)
                    {
                        _populateDataTable();
                        this._grid.DataBind();
                    }
                }
                catch (Exception exc)
                {
                    this._exc = exc;
                }
            }

            if (this._exc != null)
            {
                RegisterError(this._exc.Message);
                this._exc = null;
            }

            base.OnPreRender(e);
        }

        private void _createDataTable()
        {
            this.sourceDataTable = new DataTable();

            // The data table field that will hold URL for opening the item in Innovator
            this.sourceDataTable.Columns.Add("__Innovator_URL__");

            int count = this.SPGridDef.Columns.Count;
            for (int i = 0; i < count; i++)
            {
                string ctype = this.SPGridDef.Columns.Values[i].getProperty("column_type");
                string pname = this.SPGridDef.Columns.Values[i].getProperty("property");
                if (ctype == "item_link")
                {
                    this.sourceDataTable.Columns.Add(string.Format("__Item_Href_{0}__", pname));
                }
                this.sourceDataTable.Columns.Add(pname);
            }
        }

        private void _createSourceObject( ControlCollection cc )
        {
            //Create instance of wrapper to grab type.            
            this.myDataTable = new DataTableWrapper(this.sourceDataTable);
            Type t = myDataTable.GetType();

            //Create ObjectDataSource            
            ObjectDataSource ds = new ObjectDataSource();
            ds.ID = "myDataSource";
            ds.TypeName = t.AssemblyQualifiedName;
            ds.SelectMethod = "GetTable";
            ds.ObjectCreating += new ObjectDataSourceObjectEventHandler(ds_ObjectCreating);
            cc.Add(ds);
        }

        void oGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
        {
            this._grid.PageIndex = e.NewPageIndex;

            // NOTE: if populate table in PreRender() then paging doesn't work although
            //       PreRender() is called after this method (?!)
            _populateDataTable();
            this._grid.DataBind();
            this._data_populated = true;
        }

        private void _createSPGridView(ControlCollection cc)
        {
            this._grid = new SPGridView();
            this._grid.AutoGenerateColumns = false;
            this._grid.AllowSorting = true;

            Item qitem = this.InnovatorHandle.newItem();
            qitem.loadAML(this.SPGridDef.Query);
            this._grid.AllowPaging = true;
            this._grid.PageSize = (qitem.getAttribute("pagesize") == null) ? 20 : int.Parse(qitem.getAttribute("pagesize"));
            this._grid.PageIndexChanging += new GridViewPageEventHandler(oGrid_PageIndexChanging);
            this._grid.AlternatingRowStyle.BackColor = System.Drawing.Color.AliceBlue;
            this._grid.ID = "ArasItemGrid";
            this._grid.DataSourceID = "myDataSource";

            cc.Add(this._grid);

            for (int i = 0; i < this.SPGridDef.Columns.Count; i++)
            {
                string pname = this.SPGridDef.Columns.Values[i].getProperty("property");
                string ctype = this.SPGridDef.Columns.Values[i].getProperty("column_type");
                string align = this.SPGridDef.Columns.Values[i].getProperty("align");
                string header = this.SPGridDef.Columns.Values[i].getProperty("label");

                align = (align == null || align.Trim().Length == 0) ? "left" : align.Trim().ToLower();
                HorizontalAlign hz = HorizontalAlign.Left;
                if (align == "center")
                    hz = HorizontalAlign.Center;
                else if (align == "right")
                    hz = HorizontalAlign.Right;

                DataControlField field;
                if (i == 0 || ctype == "item_link")
                {
                    field = new HyperLinkField();
                    ((HyperLinkField)field).DataTextField = pname;
                    if (i == 0)
                        ((HyperLinkField)field).DataNavigateUrlFields = new string[] { "__Innovator_URL__" };
                    else
                        ((HyperLinkField)field).DataNavigateUrlFields = new string[] { string.Format("__Item_Href_{0}__", pname) };
                    ((HyperLinkField)field).Target = "aras_item_opener";
                }
                else
                {
                    field = new SPBoundField();
                    ((SPBoundField)field).DataField = pname;
                }

                field.SortExpression = pname;

                field.HeaderText = header;
                field.HeaderStyle.Font.Bold = true;
                field.HeaderStyle.BorderColor = System.Drawing.Color.LightGray;
                field.HeaderStyle.BorderWidth = 1;
                field.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
                
                field.ItemStyle.HorizontalAlign = hz;
                field.ItemStyle.BorderColor = System.Drawing.Color.LightGray;
                field.ItemStyle.BorderWidth = 1;
                
                this._grid.Columns.Add(field);
             }
            
            // Custom grid paging
            CustomPager pager = new CustomPager();
            pager.GridViewId = this._grid.ID;
            cc.Add(pager);
        }

        void ds_ObjectCreating(object sender, ObjectDataSourceEventArgs e) 
        { 
            this.myDataTable = new DataTableWrapper(this.sourceDataTable); 
            e.ObjectInstance = this.myDataTable; 
        }

        /// <summary>
        /// Login to Innovator.
        /// </summary>
        private Innovator _doLoginToInnovator()
        {
            WinAuthHttpServerConnection conn = IomFactory.CreateWinAuthHttpServerConnection(Url, Db);
            Item log_result = conn.Login();
            if (!log_result.isError())
                return IomFactory.CreateInnovator(conn);
            else
                throw new Exception( string.Format( "Failed login to Innovator. Original error: {0}", log_result.getErrorString() ));
        }
        
        /// <summary>
        /// Load definition of the 'SharePoint Grid' from the Innovator the web part connected to.
        /// </summary>
        /// <returns></returns>
        private GridDefinition _loadShareGridDefinition()
        {
            if (this.SharePointGrid == null || this.SharePointGrid.Trim().Length == 0)
                return null;

            if (InnovatorHandle == null)
                throw new ArgumentException("Not logged into Innovator");

            Item request = InnovatorHandle.newItem();
            string aml = string.Format("<Item type='SharePoint Grid' action='get' levels='1' configPath='SharePoint Grid Column'><name>{0}</name></Item>", this.SharePointGrid);
            request.loadAML(aml);
            Item response = request.apply();
            if (response.isError())
            {
                throw new Exception(string.Format("Failed to get definition of the 'SharePoint Grid' with name '{0}' : {1}", this.SharePointGrid, response.getErrorString()));
            }

            return new GridDefinition(response);
        }

        /// <summary>
        ///  Populating data table with data from Innovator.
        /// </summary>
        private void _populateDataTable()
        {
            DataTable dt = this.sourceDataTable;

            // Typically if user changes one of web part parameters then the page has to be
            // reloaded to cause re-login to Innovator; otherwise the handle to IOM.Innovator is null.
            //if (this.InnovatorHandle == null || this.SPGridDef == null )
            //    return;

            string query_aml = this.SPGridDef.Query;
            if (query_aml == null || query_aml.Trim().Length == 0)
                return;

            Item request = _buildRequest( query_aml );

            Item response = request.apply();
            if (response.isError())
            {
                if (response.getErrorCode() == "0")
                    return;
                else
                    throw new Exception(string.Format("ERROR: failed to query Innovator - {0}", response.getErrorString()));
            }

            int icount = response.getItemCount();
            for (int j = 0; j < icount; j++)
            {
                Item uitem = response.getItemByIndex(j);
                _addRow(uitem);
            }
        }

        private void _addRow(Item uitem)
        {
            DataRow dr = this.sourceDataTable.NewRow();
            Uri request_url = HttpContext.Current.Request.Url;
            for (int k = 0; k < this.SPGridDef.Columns.Count; k++)
            {
                string prop_name = this.SPGridDef.Columns.Values[k].getProperty("property");
                string ptype = this.SPGridDef.Columns.Values[k].getProperty("column_type");
                string dsrc = this.SPGridDef.Columns.Values[k].getProperty("data_source");
                string pval = ( uitem == null ) ? "" : uitem.getProperty(prop_name);
                switch (ptype)
                {
                    case "item_link":
                        dr[string.Format("__Item_Href_{0}__", prop_name)] = Uri.EscapeUriString(
                          //string.Format("{0}/Client/item_opener.htm?StartItem={1}:{2}&fromsite=http://demo:6684/sp_innovator", Url, dsrc, pval));
                          string.Format("{0}/Client/item_opener.htm?StartItem={1}:{2}&fromsite={3}", Url, dsrc, pval, SPUrl));
                        dr[prop_name] = (pval == null || pval.Trim().Length == 0) ? "" : uitem.getPropertyAttribute(prop_name, "keyed_name");
                        break;
                    case "item":
                        dr[prop_name] = (pval == null || pval.Trim().Length == 0) ? "" : uitem.getPropertyAttribute(prop_name, "keyed_name");
                        break;
                    case "boolean":
                        dr[prop_name] = (pval == null || pval.Trim().Length == 0 || pval.Trim().Equals("0") ? "No" : "Yes");
                        break;
                    case "date":
                        dr[prop_name] = (pval == null || pval.Trim().Length == 0) ? "" : DateTime.Parse(pval).ToShortDateString();
                        break;
                    default:
                        dr[prop_name] = pval;
                        break;
                }
            }

            string id = (uitem == null) ? "" : uitem.getID();
            string type_name = (uitem == null) ? "" : uitem.getType();
            dr["__Innovator_URL__"] = Uri.EscapeUriString(
                //string.Format("{0}/Client/item_opener.htm?StartItem={1}:{2}&fromsite=http://demo:6684/sp_innovator", Url, type_name, id));
                string.Format("{0}/Client/item_opener.htm?StartItem={1}:{2}&fromsite={3}", Url, type_name, id, SPUrl));

            this.sourceDataTable.Rows.Add(dr);
        }
        
        /// <summary>
        /// Build the request to Innovator to bring items that matches the search criteria. Note that search condition
        /// specified in UI is merged with the query condition originally specified on the 'SharePoint Grid' definition.
        /// </summary>
        /// <param name="query_aml">AML defined in the referenced 'SharePoint Grid' difinition.</param>
        /// <returns>Request item</returns>
        private Item _buildRequest( string query_aml )
        {
            Item request = InnovatorHandle.newItem();
            try
            {
                request.loadAML(query_aml);
            }
            catch (Exception exc)
            {
                throw new Exception(string.Format("ERROR: failed to load query AML of the 'SharePoint Grid' definition:\n\n{0}\n", query_aml), exc);
            }

            // Because the way SPGridView does paging and sorting it's required to bring _all_
            // items matching the search criteria every time which is not very optimal (to say the least ...).
            // In order to do so <Item>' attributes 'pagesize' and 'page' must be dropped.
            request.removeAttribute("pagesize");
            request.removeAttribute("page");

            if (this._criterion != null && this._criterion.Trim().Length > 0)
            {
                string cn = this._criterion.Trim();
                Item or_cond = request.newOR();
                for (int i = 0; i < this.SPGridDef.Columns.Count; i++)
                {
                    string prop_name = this.SPGridDef.Columns.Values[i].getProperty("property");
                    string ptype = this.SPGridDef.Columns.Values[i].getProperty("column_type");
                    string dsrc = this.SPGridDef.Columns.Values[i].getProperty("data_source");
                    if (ptype == "item" || ptype == "item_link")
                    {
                        Item subitem = InnovatorHandle.newItem();
                        subitem.loadAML(string.Format("<Item type='{0}' action='get'><keyed_name condition='like'>%{1}%</keyed_name></Item>", dsrc, cn));
                        or_cond.setPropertyItem(prop_name, subitem);
                    }
                    else if (ptype == "boolean")
                    {
                        // Because UI shows "yes"\"no" and AML contains "1"\"0" then we make an assumption that
                        // if user specified "no" or "yes" in the search text box then he\she wants to filter
                        // by the boolean field; otherwise condition for boolean fields are not set at all.
                        if( cn.ToLower() == "no" )
                        {
                            or_cond.setProperty(prop_name, "0");
                        }
                        else if (cn.ToLower() == "yes")
                        {
                            or_cond.setProperty(prop_name, "1");
                        }
                    }
                    else
                    {
                        or_cond.setPropertyCondition(prop_name, "like");
                        or_cond.setProperty(prop_name, string.Format("%{0}%", cn));
                    }
                }
            }

            return request;
        }
    }
}
